// Copyright (C) 2025, ZWSOFT Co., LTD.(Guangzhou)
// Licensed under the MIT License. See LICENSE in the project root for license information.

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Data.Sqlite;
using System.Collections.ObjectModel;
using System.IO;
using TestClient.Domain;
using TestClient.ViewModel.Util;

namespace TestClient.ViewModel;

public partial class PlyDataInput : ObservableObject
{
    public string Name { get; set; } = "";
    public string Parent { get; set; } = "";
    public string Sequence { get; set; } = "A";
    public int Step { get; set; } = 10;
    public string Orientation { get; set; } = "0";
    public string NetBoundaryId { get; set; } = "";
    public string ExtendBoundaryId { get; set; } = "";
    public string MaterialName { get; set; } = "";
}

public partial class PlySectionTestViewModel : PageViewModelBase
{
    #region Common properties
    [ObservableProperty]
    private string databasePath = "";
    [ObservableProperty]
    private string laminateName = "";
    [ObservableProperty]
    private string laminateSurfaceId = "";
    [ObservableProperty]
    private double plyScaleFactor = 1.0;
    [ObservableProperty]
    private string sectionCurveId = "";
    #endregion

    #region Ply
    [ObservableProperty]
    private string plyName = "";
    [ObservableProperty]
    private string plySequence = "A";
    [ObservableProperty]
    private int plyStep = 10;
    [ObservableProperty]
    private string plyOrientation = "0";
    [ObservableProperty]
    private string plyNetBoundaryId = "";
    [ObservableProperty]
    private string plyExtendBoundaryId = "";
    [ObservableProperty]
    private string plyMaterialName = "";

    public ObservableCollection<PlyDataInput> PlyDataRows { get; } = [];
    public ObservableCollection<string> PlyOrientationChoices { get; } = ["0", "45", "90", "-45", "0/90", "+/-45"];

    [ObservableProperty]
    private PlyDataInput? selectedItem;
    #endregion

    #region Annotation
    [ObservableProperty]
    private string annotationId = "";
    [ObservableProperty]
    private string anchorPointId = "";
    [ObservableProperty]
    private string plyBoundaryType = "Net";
    [ObservableProperty]
    private bool isAnnotationReverse = false;

    public ObservableCollection<string> PlyBoundaryTypeChoices { get; } = ["Net", "Extend"];
    #endregion

    #region Commands
    public IAsyncRelayCommand SelectLaminateSurfaceCommand { get; }
    public IAsyncRelayCommand SelectSectionCurveCommand { get; }
    public IAsyncRelayCommand SelectNetBoundaryCommand { get; }
    public IAsyncRelayCommand SelectExtendBoundaryCommand { get; }
    public IAsyncRelayCommand GenerateSectionCommand { get; }

    public IAsyncRelayCommand SelectAnchorPointCommand { get; }
    public IAsyncRelayCommand GenerateAnnotationCommand { get; }
    #endregion

    #region DB connection
    private SqliteConnection? compositeDbConn;
    private CompositeDAO? compositeDAO;
    #endregion

    public PlySectionTestViewModel(VFAdapterWrapper wrapper, ISnackNotifyService notifyService)
        : base(wrapper, notifyService)
    {
        SelectLaminateSurfaceCommand = CreateAsyncRpcCommand(SelectLaminateSurfaceAsync);
        SelectSectionCurveCommand = CreateAsyncRpcCommand(SelectSectionCurveAsync);
        SelectNetBoundaryCommand = CreateAsyncRpcCommand(SelectNetBoundaryAsync);
        SelectExtendBoundaryCommand = CreateAsyncRpcCommand(SelectExtendBoundaryAsync);
        GenerateSectionCommand = CreateAsyncRpcCommand(GenerateSectionAsync);

        SelectAnchorPointCommand = CreateAsyncRpcCommand(SelectAnchorPointAsync);
        GenerateAnnotationCommand = CreateAsyncRpcCommand(GenerateAnnotationAsync);
    }

    private static string GetCompositeDbPath(string folder) => Path.Combine(folder, "CompositeDB.db");
    private static string GetMaterialDbPath(string folder) => Path.Combine(folder, "Material.db");

    private static bool ValidateFolder(string folder)
    {
        if (!Directory.Exists(folder)) { return false; }
        if (!File.Exists(GetCompositeDbPath(folder))) { return false; }
        if (!File.Exists(GetMaterialDbPath(folder))) { return false; }
        return true;
    }

    [RelayCommand]
    private void SelectFolder()
    {
        var newFolder = DialogUtil.ShowOpenDirectoryDialog(DatabasePath);
        if (newFolder is null)
        {
            // Cancelled. Do no-op
            return;
        }

        // If the folder is invalid, show an error message
        if (!ValidateFolder(newFolder))
        {
            NotifyService.PushMessage($"Error: folder does not contain database files");
            return;
        }

        // update database connection
        DatabasePath = newFolder;
        var compositeDbPath = GetCompositeDbPath(newFolder);
        compositeDbConn = new SqliteConnection($"Data Source={compositeDbPath}");
        compositeDbConn.Open();
        compositeDAO = new CompositeDAO(compositeDbConn);
    }

    [RelayCommand]
    private void WriteLaminateTable()
    {
        if (compositeDbConn is null || compositeDAO is null)
        {
            NotifyService.PushMessage("Error: no database available");
            return;
        }
        using var transition = compositeDbConn.BeginTransaction();
        compositeDAO.InsertOrUpdateLaminate(LaminateName, LaminateSurfaceId, transition);
        transition.Commit();
    }

    [RelayCommand]
    private void WritePlyTable()
    {
        if (compositeDbConn is null || compositeDAO is null)
        {
            NotifyService.PushMessage("Error: no database available");
            return;
        }
        using var transition = compositeDbConn.BeginTransaction();
        compositeDAO.InsertOrUpdatePlies(PlyDataRows, transition);
        transition.Commit();
    }

    [RelayCommand]
    private void DeleteItem()
    {
        if (SelectedItem != null) { PlyDataRows.Remove(SelectedItem); }
    }

    [RelayCommand]
    private void AddPly()
    {
        PlyDataInput input = new()
        {
            Name = PlyName,
            Parent = LaminateName,
            Sequence = PlySequence,
            Step = PlyStep,
            Orientation = PlyOrientation,
            NetBoundaryId = PlyNetBoundaryId,
            ExtendBoundaryId = PlyExtendBoundaryId,
            MaterialName = PlyMaterialName
        };
        PlyDataRows.Add(input);
    }

    [RelayCommand]
    private void ClearPly()
    {
        PlyDataRows.Clear();
    }

    private async Task SelectLaminateSurfaceAsync()
    {
        EnsureConnected();
        LaminateSurfaceId = await ViewModelHelpers.SelectEnt(Wrapper, "Surface");
    }

    private async Task SelectSectionCurveAsync()
    {
        EnsureConnected();
        SectionCurveId = await ViewModelHelpers.SelectEnt(Wrapper, "Curve");
    }

    private async Task SelectNetBoundaryAsync()
    {
        EnsureConnected();
        PlyNetBoundaryId = await ViewModelHelpers.SelectEnt(Wrapper, "Curve");
    }

    private async Task SelectExtendBoundaryAsync()
    {
        EnsureConnected();
        PlyExtendBoundaryId = await ViewModelHelpers.SelectEnt(Wrapper, "Curve");
    }

    private async Task GenerateSectionAsync()
    {
        EnsureConnected();
        var sectionParameter = new VFAdapterWrapper.PlySectionInput(
            LaminateName: LaminateName,
            CrossSectionCurveId: SectionCurveId,
            Scale: PlyScaleFactor,
            DbPath: DatabasePath
            );

        await Task.Run(() =>
        {
            Wrapper.Create3DSection(sectionParameter);
        });
    }

    private async Task SelectAnchorPointAsync()
    {
        EnsureConnected();
        AnchorPointId = await ViewModelHelpers.SelectEnt(Wrapper, "Point");
    }

    private async Task GenerateAnnotationAsync()
    {
        EnsureConnected();
        var annotationParam = new VFAdapterWrapper.Ply3DNoteInput(
            LaminateName: LaminateName,
            AnnotationLeader: AnnotationId,
            RefOriginId: AnchorPointId,
            BoundaryType: PlyBoundaryType,
            ReverseSequence: IsAnnotationReverse,
            DbPath: DatabasePath
            );

        await Task.Run(() =>
        {
            Wrapper.Create3DNotes(annotationParam);
        });
    }
}
